
\section{Explique como se genera un driver de dispositivo tipo bloque y como se realiza la
vinculación con el sistema de archivos en MINIX.}
\textbf{ Ademá s describa la estructura del procedimiento
de una tarea de E/S (I/O task).
}
\textbf{Sugerencias: Como ayuda busque las respuestas a las siguientes preguntas:}
\begin{itemize}
\item        \textbf{Qué es el “major number” de un device. (com.h)}
\item         \textbf{Dónde se declara la entrada a un driver. (table.c)}
\item        \textbf{Cómo se llega desde un proceso usuario al driver. (table.c)}                                        \end{itemize}
Los procedimientos principales de toda tarea de \textbf{entrada}/\textbf{salida} tienen una
estructura similar. MINIX siempre tuvo al menos tres tareas de dispositivos de
bloques:
\begin{enumerate}
  \item el driverr RAM disk1
  \item el del floppy disk
  \item algún driver de disco
\end{enumerate}
ya compilados en el sistema. Aunque el driver para cada uno de estos ejecute como un
proceso independiente, como están todos compilados como parte del kernel
hace que una porción considerable de código sea compartida.
Cada controlador de dispositivos de bloque tiene que hacer algún tipo de
inicialización, pero luego de esto cada driver llama a la función que
contiene el ciclo común principal. Este bucle
nunca termina, por lo que y nunca vuelve al llamador.

Este tipo de tareas tiene esta estructura:
\begin{verbatim}
Mientras (TRUE){
       recibo un mensaje;
       lo proceso;
       genero un mensaje de respuesta;
}
\end{verbatim}

Existe una única copia del \textit{loop} principal en los binarios de MINIX. 
La técnica consiste en que cada controlador le pase como parámetro al ciclo principal un puntero a una tabla. Esta tabla contiene las direcciones de las funciones que el
controlador usará para cada operación. La tabla es una estructura llamada driver
que se encuentra en \textit{driver.h}.
De esta manera las funciones son invocadas indirectamente, y hasta es posible
compartir funciones.
Podemos ver el ciclo principal:
\begin{verbatim}
message mess;    /*buffer para el mensaje*/
void shared_io_task(struct driver_table *entry_points){
/*todas las tareas inicializan antes de invocar esta función*/
        while(TRUE){
                   receive(ANY, &mess);
                   caller = mess.source;
                   switch (mess.type) {
                             case READ:
                                rcode = (*entry_points -> dev_read)(&mess);
                                      break;
                             case WRITE:
                                rcode = (*entry_points -> dev_write)(&mess);
                                      break;
                             default:
                                rcode= ERROR;
                   }
          mess.type = TASK_REPLY;
          mess.status = rcode;
          send(caller, &mess);
          }
}
\end{verbatim}
 La sentencia \verb| rcode = (*entry\_points -> dev\_read)(&mess); | es una llamada a
función indirecta. Una función \textit{dev\_read} diferente es invocada para cada driver.
Funciones más sencillas como CLOSE podrían ser la misma para varios drivers.

Existen seis operaciones posibles
\begin{enumerate}
 \item OPEN
\item CLOSE
\item READ
\item WRITE
\item IOCTL
\item SCATTERED\_IO
\end{enumerate}

READ y WRITE con las ya conocidas, que en este caso transfieren bloques de datos
del dispositivo a memoria, o viceversa. Un OPEN debería verificar si el dispositivo es
accesible y devolver un error en caso contrario, y un CLOSE debería garantizar que
cualquier buffer con datos termine de transferirse. La operación IOCTL no es muy
conocida, pero se puede utilizar por ejemplo para cambiar la manera en que un
disco es particionado. Finalmente SCATTERED\_IO le permite al file system hacer un
pedido de lectura o escritura de múltiples bloques, de manera que el acceso se
optimice según el dispositivo, aunque luego no todos estos datos sean utilizados.


En \textit{src/kernel/driver.h} se encuentra todas las definiciones que un controlador de
dispositivo de bloques necesita, como la estructura driver mencionada con
anterioridad (cuando creamos un nuevo driver debemos usar esta estructura,
proveyendo punteros a las funciones open, close, ioctl, prepare, schedule, finish,
cleanup y geometry). También encontramos definida la estructura device que
contiene información importante sobre particiones, como la dirección base y el
tamaño en bytes.

La función que es llamada por todos los controladores y tiene el ciclo principal es
\textit{driver\_task}, y se encuentra en \textit{src/kernel/driver.c} junto con otras funciones compartidas, tales como \textit{do\_rdwt} y \textit{do\_vrdwt} las cuales son invocadas por
\textit{driver\_task} siempre antes de llamar a las funciones READ, WRITE o SCATTERED\_IO
propias del controlador, que básicamente lo que hacen es copiar el pedido de
entrada/salida del espacio del usuario al espacio del kernel.

Al driver se le asigna un \textbf{número mayor} de dispositivo que indica la clase o grupo,
un \textbf{número menor} que señala el dispositivo específico (con este último se pueden
manejar las distintas particiones/unidades). En caso de necesitar usar un
controlador DMA (con su respectiva limitación de 64 KB) contamos con un puntero
\textit{tmp\_buf} que apunta al buffer de DMA.

\textbf{Agregando el driver}                          

Una vez creado el driver debemos seguir los pasos siguientes para agregarlo.
Primero lo guardamos en \textit{/usr/src/kernel} y modificamos apropiadamente el Makefile
de dicho directorio. Luego debemos enlazar el archivo con el resto del kernel.
Para esto comenzamos modificando el tamaño de la pila que el kernel usa para las
tareas (esta pila es única para todas las tareas), entonces en el \textit{src/kernel/table.c}
escribimos:

\verb|#define MYTASK_STACK ( SMALL_STACK * 4)|
también modificamos la línea:
\verb|#define TOT_STACK_SPACE ( … + MYTASK_STACK) |
y agregamos la tarea al array tasktab, la agregamos luego de la tarea tty,
tipeando:

\textbf{función\_de\_entrada\_de\_la\_tarea, MYTASK\_STACK, ``nombre\_de\_mi\_tarea''}\footnote{donde función\_de\_entrada\_de\_la\_tarea es la que llama a driver\_task}

. Ahora debemos asignarle un número a nuestra tarea, para esto modificamos el archivo
\textit{include/minix/com.h} agregando un define para nuestra tarea y modificando el de la
tty

\verb|#define TTY (MYTASK – 1 )|

\verb|#define MYTASK (DL\_ETH -1)|

Finalmente debemos incrementar en uno la constante llamada \textit{NR\_TASK} que define
la cantidad de tareas del sistema, ubicada en el archivo \textit{incluye/minix/const.h}.

Como último paso, debemos vincular el driver con el file system. Para esto
debemos agregar una entrada en el array dmap, para asociar un major number,
entonces la tipeamos al final del array:

\verb|DT(1, dev_opcl, call_task, dev_opcl, NUMERO_DE_TAREA)|

donde el primer parámetro indica que esta activada y el último es el numero de
tarea que le asignamos en el archivo com.h.
Ahora necesitamos crear el archivo especial con le comando mknod, corriendo lo
siguiente:

\verb|mknod /dev/myDevice b MAJOR_NUMBER MINOR_NUMBER.|

Eso es todo, se creo e incluyo en MINIX nuestro propio driver.
